package com.lhl.api.exception;

import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.lhl.fw.core.base.ResultDTO;
import com.lhl.fw.core.exception.BadRequestException;
import com.lhl.fw.core.exception.BizException;
import com.lhl.fw.core.exception.CommonErrorCode;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @className: com.lhl.api.exception.GlobalBaseExceptionHandler
 * @description: 基础异常抛出
 * 包含 类型转换异常  数组下表越界 文件没有找到 输入输出  空指针  参数校验 自定义异常 除数为零
 * @author: king
 * @date: 2021-01-09 22:10
 **/
@Api(tags = "基础异常处理", produces = "GlobalBaseExceptionHandler")
@Slf4j
@RestControllerAdvice
public class GlobalBaseExceptionHandler {
    /**
     * 类转换异常
     * @param exception
     * @return
     */
    @ExceptionHandler(ClassCastException.class)
    public ResultDTO classCastException(ClassCastException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.CLASS_CAST_EXCEPTION).build();
    }

    /**
     * 数组下表越界异常
     * @param exception
     * @return
     */
    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    public ResultDTO arrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.ARR_EXCEPTION).build();
    }

    /**
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(FileNotFoundException.class)
    public ResultDTO fileNotFoundException(FileNotFoundException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.FILE_NOT_FOUND_EXCEPTION).build();
    }

    /**
     * 输入输出流异常
     * @param exception
     * @return
     */
    @ExceptionHandler(IOException.class)
    public ResultDTO iOException(IOException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.IO_Exception).build();
    }

    /**
     * 空指针异常
     * @param exception
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public ResultDTO nullPointerException(NullPointerException exception) {
        exception.printStackTrace();
        return ResultDTO.getFailDto(CommonErrorCode.NULL_EXCEPTION).build();
    }

    /**
     * 除数为零
     * @param exception
     * @return
     */
    @ExceptionHandler(ArithmeticException.class)
    public ResultDTO arithmeticException(ArithmeticException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.DIVISOR_IS_ZERO_EXCEPTION).build();
    }

    /**
     * IllegalArgumentException 使用断言Assert捕获异常信息
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    public ResultDTO handlerIllegalArgumentException(IllegalArgumentException e) {
        return ResultDTO.getFailDto()
                .msg(e.getMessage()).build();
    }

    /**
     * 自定义异常
     * @param exception
     * @return
     */
    @ExceptionHandler(BizException.class)
    public ResultDTO customException(BizException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.DEFAULT_ERROR).build();
    }

    @ExceptionHandler(BadRequestException.class)
    public ResultDTO badRequestException(BadRequestException exception) {
        return ResultDTO.getFailDto(CommonErrorCode.USER_NOT_FOUND).build();
    }

    /**
     * HttpMessageNotReadableException 参数错误异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResultDTO handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        outPutError(HttpMessageNotReadableException.class, CommonErrorCode.INVALID_REQUEST_PARAM_ERROR, e);
        String msg = StrUtil.format("{} : 错误详情( {} )", CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getMessage(),
                e.getRootCause().getMessage());
        return ResultDTO.getFailDto(CommonErrorCode.INVALID_REQUEST_PARAM_ERROR)
                .msg(msg).build();
    }

    /**
     * 绑定参数异常
     * 对方法参数校验异常处理方法(仅对于表单提交有效，对于以json格式提交将会失效)
     * 如果是表单类型的提交，则spring会采用表单数据的处理类进行处理（进行参数校验错误时会抛出BindException异常）
     */
    @ExceptionHandler(BindException.class)
    public ResultDTO handlerBindException(BindException exception) {
        return handlerNotValidException(exception);
    }

    /**
     * 对方法参数校验异常处理方法(前端提交的方式为json格式出现异常时会被该异常类处理)
     * json格式提交时，spring会采用json数据的数据转换器进行处理（进行参数校验时错误是抛出MethodArgumentNotValidException异常）
     *
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultDTO handlerArgumentNotValidException(MethodArgumentNotValidException exception) {
        return handlerNotValidException(exception);
    }

    private ResultDTO handlerNotValidException(Exception e) {
        log.debug("begin resolve argument exception");
        BindingResult result;
        if (e instanceof BindException) {
            BindException exception = (BindException) e;
            result = exception.getBindingResult();
        } else {
            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
            result = exception.getBindingResult();
        }

        Map<String, String> errors = Maps.newHashMap();
        if (result.hasErrors()) {
            List<FieldError> fieldErrors = result.getFieldErrors();
            if (log.isDebugEnabled()) {
                for (FieldError error : fieldErrors) {
                    log.error("{} -> {}", error.getField(), error.getDefaultMessage());
                }
            }
            fieldErrors.forEach(error -> {
                errors.put(error.getField(), error.getDefaultMessage());
            });
        }
        return ResultDTO.getFailDto()
                .code(CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getCode())
                .errors(errors).build();
    }

    private void outPutError(Class errorType, Enum secondaryErrorType, Throwable throwable) {
        log.error("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage(),
                throwable);
    }

    private void outPutErrorWarn(Class errorType, Enum secondaryErrorType, Throwable throwable) {
        log.warn("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage());
    }
}
